home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1995 June / MacFormat 25.iso / Shareware City / Developers / OutOfPhase1.1 Source / OutOfPhase Folder / Level 0 Macintosh 01Jan95 / SoundOutput.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-10-27  |  13.9 KB  |  447 lines  |  [TEXT/KAHL]

  1. /* SoundOutput.c */
  2. /*****************************************************************************/
  3. /*                                                                           */
  4. /*    System Dependency Library for Building Portable Software               */
  5. /*    Macintosh Version                                                      */
  6. /*    Written by Thomas R. Lawrence, 1993 - 1994.                            */
  7. /*                                                                           */
  8. /*    This file is Public Domain; it may be used for any purpose whatsoever  */
  9. /*    without restriction.                                                   */
  10. /*                                                                           */
  11. /*    This package is distributed in the hope that it will be useful,        */
  12. /*    but WITHOUT ANY WARRANTY; without even the implied warranty of         */
  13. /*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                   */
  14. /*                                                                           */
  15. /*    Thomas R. Lawrence can be reached at tomlaw@world.std.com.             */
  16. /*                                                                           */
  17. /*****************************************************************************/
  18.  
  19. #include "MiscInfo.h"
  20. #include "Debug.h"
  21. #include "Audit.h"
  22. #include "Definitions.h"
  23.  
  24. #ifdef THINK_C
  25.     #pragma options(pack_enums)
  26. #endif
  27. #include <Sound.h>
  28. #include <Errors.h>
  29. #ifdef THINK_C
  30.     #pragma options(!pack_enums)
  31. #endif
  32.  
  33. #include "SoundOutput.h"
  34. #include "Memory.h"
  35.  
  36.  
  37. #define MinimumInitialNumberOfBuffers (3)
  38.  
  39. typedef struct MyStructure
  40.     {
  41.         struct MyStructure*        Next;
  42.         struct MyStructure*        Previous;
  43.         SndCommand                        MySoundCommand;
  44.         SndCommand                        MyCallbackCommand;
  45.         volatile MyBoolean        InUseFlag; /* interrupt level flag; hence "volatile" */
  46.         ExtSoundHeader                Header;
  47.         char*                                    SampleArea;
  48.     } MyStructure;
  49.  
  50.  
  51. static MyBoolean            SoundSystemInUse = False;
  52.  
  53. static MyStructure*        NextAvailableBuffer;
  54. static MyBoolean            IsCurrentBufferCheckedOut;
  55.  
  56. static long                        MaxFramesPerBuffer;
  57. static int                        BytesPerFrame;
  58. static int                        MaxBuffersToAllocate;
  59. static int                        CurrentBufferCount;
  60. static long                        TheSamplingRate;
  61. static MyBoolean            StereoFlag;
  62. static MyBoolean            SixteenBitFlag;
  63.  
  64. static SndChannel*        MySoundChannel;
  65.  
  66.  
  67. static void                            AllocateANewBuffer(void);
  68. static void                            DisposeBuffers(void);
  69. static pascal void            MyCallBack(SndChannel* Channel, SndCommand* Command);
  70.  
  71.  
  72. /* attempt to obtain a sound channel.  returns True if the sound channel was opened */
  73. /* or False if it couldn't be (if already in use or machine doesn't support sound) */
  74. MyBoolean        OpenSoundChannel(long SamplingRate, SoundOutputStereo WantStereo,
  75.                             SoundOutputNumBits NumBits, long FramesPerBuffer, int MaxNumBuffers,
  76.                             int InitialNumBuffers)
  77.     {
  78.         OSErr                            Error;
  79.         SndCommand                Cmd;
  80.         long                            InitOptions;
  81.  
  82.         /* open the sound channel */
  83.         if (SoundSystemInUse)
  84.             {
  85.                 return False;
  86.             }
  87.         InitOptions = initNoInterp /* | initNoDrop */;
  88.         if (WantStereo == eStereo)
  89.             {
  90.                 InitOptions |= initStereo;
  91.             }
  92.         MySoundChannel = NIL;
  93.         Error = SndNewChannel(&MySoundChannel,sampledSynth,InitOptions,
  94.             (SndCallBackProcPtr)&MyCallBack);
  95.         if (Error == noErr)
  96.             {
  97.                 /* we want maximum volume on the sound channel */
  98.                 Cmd.cmd = ampCmd;
  99.                 Cmd.param1 = 255;
  100.                 Cmd.param2 = 0;
  101.                 Error = SndDoImmediate(MySoundChannel,&Cmd);
  102.                 if (Error == noErr)
  103.                     {
  104.                         /* initialize the variables */
  105.                         TheSamplingRate = SamplingRate;
  106.                         StereoFlag = (WantStereo == eStereo);
  107.                         SixteenBitFlag = (NumBits == e16bit);
  108.                         NextAvailableBuffer = NIL;
  109.                         MaxFramesPerBuffer = FramesPerBuffer;
  110.                         BytesPerFrame = 1;
  111.                         if (StereoFlag)
  112.                             {
  113.                                 BytesPerFrame *= 2; /* double the number of words per sample frame */
  114.                             }
  115.                         if (SixteenBitFlag)
  116.                             {
  117.                                 BytesPerFrame *= (sizeof(short) / sizeof(char)); /* words, not bytes */
  118.                             }
  119.                         MaxBuffersToAllocate = MaxNumBuffers;
  120.                         CurrentBufferCount = 0;
  121.                         IsCurrentBufferCheckedOut = False;
  122.                         /* allocate the buffers that were requested to begin with */
  123.                         if (InitialNumBuffers < MinimumInitialNumberOfBuffers)
  124.                             {
  125.                                 InitialNumBuffers = MinimumInitialNumberOfBuffers;
  126.                             }
  127.                         while (InitialNumBuffers > 0)
  128.                             {
  129.                                 AllocateANewBuffer();
  130.                                 InitialNumBuffers -= 1;
  131.                             }
  132.                         /* notice that we escape here */
  133.                         if (CurrentBufferCount > 1)
  134.                             {
  135.                                 /* we need at least 2 buffers, otherwise the sound will skip so */
  136.                                 /* there'd be no point in doing it with 1 buffer.  Other systems */
  137.                                 /* may not have this restriction (e.g. UNIX) */
  138.                                 SoundSystemInUse = True;
  139.                                 return True;
  140.                             }
  141.                         DisposeBuffers();
  142.                     }
  143.                 SndDisposeChannel(MySoundChannel,True);
  144.             }
  145.         return False;
  146.     }
  147.  
  148.  
  149. /* internal routine to add a buffer to the existing ring of buffers. */
  150. static void                AllocateANewBuffer(void)
  151.     {
  152.         MyStructure*        Buffer;
  153.         unsigned long        Mantissa;
  154.         unsigned long        Exponent;
  155.  
  156.         Buffer = (MyStructure*)AllocPtrCanFail(sizeof(MyStructure),"SoundBufHeader");
  157.         if (Buffer != NIL)
  158.             {
  159.                 Buffer->SampleArea = AllocPtrCanFail(MaxFramesPerBuffer
  160.                     * BytesPerFrame,"SndBuffer");
  161.                 if (Buffer->SampleArea == NIL)
  162.                     {
  163.                         ReleasePtr((char*)Buffer);
  164.                         return;
  165.                     }
  166.                 Buffer->InUseFlag = False;
  167.                 Buffer->Header.samplePtr = Buffer->SampleArea;
  168.                 if (SixteenBitFlag)
  169.                     {
  170.                         Buffer->Header.sampleSize = 16;
  171.                     }
  172.                  else
  173.                     {
  174.                         Buffer->Header.sampleSize = 8;
  175.                     }
  176.                 if (StereoFlag)
  177.                     {
  178.                         Buffer->Header.numChannels = 2;
  179.                     }
  180.                  else
  181.                     {
  182.                         Buffer->Header.numChannels = 1;
  183.                     }
  184.                 Buffer->Header.sampleRate = (unsigned long)TheSamplingRate << 16;
  185.                 Buffer->Header.loopStart = 0;
  186.                 Buffer->Header.loopEnd = 0;
  187.                 Buffer->Header.encode = extSH;
  188.                 Buffer->Header.baseFrequency = 64;
  189.                 Buffer->Header.markerChunk = NULL;
  190.                 Buffer->Header.futureUse1 = 0;
  191.                 Buffer->Header.futureUse2 = 0;
  192.                 Buffer->Header.futureUse3 = 0;
  193.                 Buffer->Header.futureUse4 = 0;
  194.                 /* extended 22050 = 400D AC44000000000000 */
  195.                 /* extended 22051 = 400D AC46000000000000 */
  196.                 /* extended 44100 = 400E AC44000000000000 */
  197.                 /* extended 44101 = 400E AC45000000000000 */
  198.                 Exponent = 0x401e;
  199.                 Mantissa = TheSamplingRate;
  200.                 while ((Mantissa & 0x80000000) == 0)
  201.                     {
  202.                         Mantissa = Mantissa << 1;
  203.                         Exponent -= 1;
  204.                     }
  205.                 ((char*)&(Buffer->Header.AIFFSampleRate))[0] = (Exponent >> 8) & 0xff;
  206.                 ((char*)&(Buffer->Header.AIFFSampleRate))[1] = Exponent & 0xff;
  207.                 ((char*)&(Buffer->Header.AIFFSampleRate))[2] = (Mantissa >> 24) & 0xff;
  208.                 ((char*)&(Buffer->Header.AIFFSampleRate))[3] = (Mantissa >> 16) & 0xff;
  209.                 ((char*)&(Buffer->Header.AIFFSampleRate))[4] = (Mantissa >> 8) & 0xff;
  210.                 ((char*)&(Buffer->Header.AIFFSampleRate))[5] = Mantissa & 0xff;
  211.                 ((char*)&(Buffer->Header.AIFFSampleRate))[6] = 0;
  212.                 ((char*)&(Buffer->Header.AIFFSampleRate))[7] = 0;
  213.                 ((char*)&(Buffer->Header.AIFFSampleRate))[8] = 0;
  214.                 ((char*)&(Buffer->Header.AIFFSampleRate))[9] = 0;
  215.                 /* now, link the buffer */
  216.                 if (NextAvailableBuffer != NIL)
  217.                     {
  218.                         /* link this block in */
  219.                         Buffer->Next = NextAvailableBuffer;
  220.                         Buffer->Previous = NextAvailableBuffer->Previous;
  221.                         /* link enclosing buffers to it */
  222.                         NextAvailableBuffer->Previous->Next = Buffer;
  223.                         NextAvailableBuffer->Previous = Buffer;
  224.                         /* stick it where we can use it */
  225.                         NextAvailableBuffer = Buffer;
  226.                     }
  227.                  else
  228.                     {
  229.                         Buffer->Next = Buffer;
  230.                         Buffer->Previous = Buffer;
  231.                         NextAvailableBuffer = Buffer;
  232.                     }
  233.                 CurrentBufferCount += 1;
  234.             }
  235.     }
  236.  
  237.  
  238. /* internal routine to dispose of all of the buffers */
  239. /* don't call this unless they're InUseFlag is clear!!! */
  240. static void                DisposeBuffers(void)
  241.     {
  242.         while (NextAvailableBuffer != NIL)
  243.             {
  244.                 MyStructure*        Temp;
  245.  
  246.                 Temp = NextAvailableBuffer;
  247.                 if (NextAvailableBuffer->Next == NextAvailableBuffer)
  248.                     {
  249.                         /* degenerate */
  250.                         NextAvailableBuffer = NIL;
  251.                     }
  252.                  else
  253.                     {
  254.                         NextAvailableBuffer->Previous->Next = NextAvailableBuffer->Next;
  255.                         NextAvailableBuffer->Next->Previous = NextAvailableBuffer->Previous;
  256.                         NextAvailableBuffer = NextAvailableBuffer->Next;
  257.                     }
  258.                 ReleasePtr(Temp->SampleArea);
  259.                 ReleasePtr((char*)Temp);
  260.             }
  261.     }
  262.  
  263.  
  264. #ifdef THINK_C
  265.     #if __option(profile)
  266.         #define Profiling (True)
  267.     #else
  268.         #define Profiling (False)
  269.     #endif
  270.  
  271.     #pragma options(!profile)
  272. #endif
  273.  
  274. /* asynchronous callback routine which marks buffers as now unused */
  275. /* It would be very bad to profile this since profiling tampers with the */
  276. /* stack invocation and uses A5 global variables!  (believe me, I've tried) */
  277. static pascal void    MyCallBack(SndChannel* Channel, SndCommand* Command)
  278.   {
  279.     *(MyBoolean*)(Command->param2) = 0;
  280.   }
  281.  
  282. #ifdef THINK_C
  283.     #if Profiling
  284.         #pragma options(profile)
  285.     #endif
  286. #endif
  287.  
  288.  
  289. /* close the sound channel and clean up the buffers */
  290. /* waits until all buffers have played out */
  291. void                CloseSoundChannel(void (*Callback)(void* Refcon), void* Refcon)
  292.     {
  293.         MyStructure*        Scan;
  294.  
  295.         ERROR(!SoundSystemInUse,PRERR(ForceAbort,
  296.             "CloseSoundChannel called, but sound channel isn't open"));
  297.         SoundSystemInUse = False;
  298.      LoopPoint:
  299.         Scan = NextAvailableBuffer;
  300.         do
  301.             {
  302.                 if (Scan->InUseFlag)
  303.                     {
  304.                         if (Callback != NIL)
  305.                             {
  306.                                 (*Callback)(Refcon);
  307.                             }
  308.                         goto LoopPoint;
  309.                     }
  310.                 Scan = Scan->Next;
  311.             } while (Scan != NextAvailableBuffer /* "Full Circle" */);
  312.         /* dispose of the blasted thing */
  313.         SndDisposeChannel(MySoundChannel,True/*shutupnow*/);
  314.         /* release the memory */
  315.         DisposeBuffers();
  316.     }
  317.  
  318.  
  319. /* obtain a pointer to one of the [nonrelocatable] sound buffers.  Data in this */
  320. /* buffer is interpreted as such:  For 8-bit mono, the buffer is an array of */
  321. /* signed chars.  For 16bit mono, the buffer is an array of 2-byte signed integers in */
  322. /* the machine's native endianness.  For 8-bit stereo, the buffer is an array of */
  323. /* 2-byte tuples; the byte lower in memory is the left channel.  For 16-bit stereo, */
  324. /* the buffer is an array of 2-(2-byte) tuples, the left channel is lower in memory. */
  325. /* If there are no buffers currently available (and new ones couldn't be allocated) */
  326. /* then it returns NIL */
  327. char*                CheckOutSoundBuffer(void)
  328.     {
  329.         ERROR(!SoundSystemInUse,PRERR(ForceAbort,
  330.             "CheckOutSoundBuffer called, but sound channel isn't open"));
  331.         ERROR(IsCurrentBufferCheckedOut,PRERR(ForceAbort,
  332.             "CheckOutSoundBuffer called while a buffer has already been checked out"));
  333.         if (NextAvailableBuffer->InUseFlag)
  334.             {
  335.                 /* oops, buffer is in use, try to make another */
  336.                 if (CurrentBufferCount < MaxBuffersToAllocate)
  337.                     {
  338.                         AllocateANewBuffer();
  339.                     }
  340.                 if (NextAvailableBuffer->InUseFlag)
  341.                     {
  342.                         return NIL;
  343.                     }
  344.             }
  345.         IsCurrentBufferCheckedOut = True;
  346.         return NextAvailableBuffer->SampleArea;
  347.     }
  348.  
  349.  
  350. /* submit a buffer to be queued to the system's sound channel.  The number of frames */
  351. /* in the buffer actually used is specified to allow less than the full buffer to */
  352. /* be used. */
  353. void                SubmitBuffer(char* Buffer, long NumUsedFrames,
  354.                             void (*Callback)(void* Refcon), void* Refcon)
  355.     {
  356.         OSErr            Error;
  357.         long            Scan;
  358.  
  359.         ERROR(!SoundSystemInUse,PRERR(ForceAbort,
  360.             "SubmitBuffer called, but sound channel isn't open"));
  361.         ERROR(!IsCurrentBufferCheckedOut,PRERR(ForceAbort,
  362.             "SubmitBuffer called but no buffer has been checked out"));
  363.         ERROR(Buffer != NextAvailableBuffer->SampleArea,
  364.             PRERR(ForceAbort,"SubmitBuffer:  Wrong buffer was submitted"));
  365.         ERROR((NumUsedFrames < 0) || (NumUsedFrames > MaxFramesPerBuffer),
  366.             PRERR(ForceAbort,"SubmitBuffer:  Number of used frames exceeds buffer size!"));
  367.         IsCurrentBufferCheckedOut = False;
  368.         ERROR(NextAvailableBuffer->InUseFlag,PRERR(ForceAbort,
  369.             "SubmitBuffer:  Internal error -- InUseFlag is set but shouldn't be"));
  370.         /* adjust the signed samples to be unsigned */
  371.         /* due to Apple's silliness, this only needs to be done for 8-bit samples */
  372.         if (StereoFlag)
  373.             {
  374.                 if (!SixteenBitFlag)
  375.                     {
  376.                         /* stereo, 8-bit */
  377.                         for (Scan = (2 * NumUsedFrames) - 1; Scan >= 0; Scan -= 1)
  378.                             {
  379.                                 ((char*)Buffer)[Scan] += 0x80;
  380.                             }
  381.                     }
  382.             }
  383.          else
  384.             {
  385.                 if (!SixteenBitFlag)
  386.                     {
  387.                         /* mono, 8-bit */
  388.                         for (Scan = NumUsedFrames - 1; Scan >= 0; Scan -= 1)
  389.                             {
  390.                                 ((char*)Buffer)[Scan] += 0x80;
  391.                             }
  392.                     }
  393.             }
  394.         /* submit the command to actually play the buffer */
  395.      TryAgainPoint1:
  396.         NextAvailableBuffer->InUseFlag = True;
  397.         NextAvailableBuffer->MySoundCommand.cmd = bufferCmd;
  398.         NextAvailableBuffer->MySoundCommand.param1 = 0;
  399.         NextAvailableBuffer->MySoundCommand.param2 = (long)&(NextAvailableBuffer->Header);
  400.         NextAvailableBuffer->Header.numFrames = NumUsedFrames;
  401.         Error = SndDoCommand(MySoundChannel,&(NextAvailableBuffer->MySoundCommand),True);
  402.         if (queueFull == Error)
  403.             {
  404.                 /* oops, we filled up the OS queue; wait a little and try again */
  405.                 if (Callback != NIL)
  406.                     {
  407.                         (*Callback)(Refcon);
  408.                     }
  409.                 goto TryAgainPoint1;
  410.             }
  411.         /* submit the callback routine request.  the callback is an interrupt level */
  412.         /* thing that clears a buffer so we can use it again. */
  413.      TryAgainPoint2:
  414.         NextAvailableBuffer->MyCallbackCommand.cmd = callBackCmd;
  415.         /* say where to store the "0" */
  416.         NextAvailableBuffer->MyCallbackCommand.param2
  417.             = (long)&(NextAvailableBuffer->InUseFlag);
  418.         Error = SndDoCommand(MySoundChannel,&(NextAvailableBuffer->MyCallbackCommand),True);
  419.         if (queueFull == Error)
  420.             {
  421.                 if (Callback != NIL)
  422.                     {
  423.                         (*Callback)(Refcon);
  424.                     }
  425.                 goto TryAgainPoint2;
  426.             }
  427.         /* advance to the next buffer */
  428.         NextAvailableBuffer = NextAvailableBuffer->Next;
  429.     }
  430.  
  431.  
  432. /* discard all queued data on the sound channel and close it immediately */
  433. void                KillSoundChannel(void)
  434.     {
  435.         ERROR(!SoundSystemInUse,PRERR(ForceAbort,
  436.             "KillSoundChannel:  but sound channel isn't open"));
  437.  
  438.         /* dispose of the sound channel */
  439.         SndDisposeChannel(MySoundChannel,True/*shutupnow*/);
  440.  
  441.         /* release buffers */
  442.         DisposeBuffers();
  443.  
  444.         /* mark sound subsystem as available */
  445.         SoundSystemInUse = False;
  446.     }
  447.